package com.boot.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.boot.mall.product.service.CategoryService;
import com.boot.mall.product.vo.Catalog2Vo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boot.common.utils.PageUtils;
import com.boot.common.utils.Query;

import com.boot.mall.product.dao.CategoryDao;
import com.boot.mall.product.entity.CategoryEntity;
import org.springframework.util.StringUtils;


@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 查询所有的分类信息，以树形结构显示
    @Override
    public List<CategoryEntity> listWithTree() {
        //1.从数据库中查询所有的分类信息
        List<CategoryEntity> data = baseMapper.selectList(null);
        // 创建一级分类的list集合
        List<CategoryEntity> categoryEntities = new ArrayList<>();
        // 对data进行遍历判断是否是一级分类
        for (CategoryEntity categoryEntity : data) {
            if (categoryEntity.getParentCid().equals("0")){
                // 添加到list中
                categoryEntities.add(categoryEntity);
            }
        }
        //2.对一级分类集合进行遍历，调用方法查询子分类
        for (CategoryEntity categoryEntity : categoryEntities) {
            categoryEntity.setChildren(getChildren(categoryEntity.getCatId(),data));
        }
        return categoryEntities;
    }

    // 遍历查询分类的子分类
    private List<CategoryEntity> getChildren(String catId, List<CategoryEntity> data) {
        // 创建分类集合
        List<CategoryEntity> categoryList = new ArrayList<>();
        // 遍历data集合，如果分类的父分类id等于catId，则添加到集合当中
        for (CategoryEntity entity : data) {
            if (entity.getParentCid().equals(catId)){
                categoryList.add(entity);
            }
        }
        // 遍历分类集合，查询分类的子分类信息
        for (CategoryEntity categoryEntity : categoryList) {
            // 查询分类信息
            categoryEntity.setChildren(getChildren(categoryEntity.getCatId(),data));
        }
        // 递归退出条件
        if (categoryList.size() == 0){
            return null;
        }
        return categoryList;
    }

    /*-------------------------------------------------------------------------------*/

    //查询一级分类信息
    @Cacheable(value = "category",key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Catagories() {
        List<CategoryEntity> parent_cid = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return parent_cid;
    }

    //引入redis中间件进行查询所有分类信息
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {

        //TODO 1、空结果缓存：解决缓存穿透
        //TODO 2、设置随机过期时间：解决缓存雪崩
        //TODO 3、加锁：解决缓存击穿

        //1.从Redis中查询是否含有 catalogJson数据
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)){
            System.out.println("即将执行查询数据库！");
            //2.查询结果为空时，再从数据库查询，并给Redis中存储一份
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDBWithRedisLock();
            return catalogJsonFromDB;
        }else {
            //3.查询结果不为空，直接从Redis获取数据并转换为Map形式返回
            System.out.println("Redis命中数据！");
            Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>(){});
            return map;
        }
    }





    //简单的使用分布式锁
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {
        //1.生成一个线程的UUID，设置成锁的value值
        String uuid = UUID.randomUUID().toString();
        //2.在Redis中创建一个锁，设置过期时间为3s，创建锁时，设置加锁时间和创建锁必须是原子性的，避免出现死锁情况
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock){
            System.out.println("抢占分布式锁成功！即将执行业务...");
            //加锁成功，执行业务
            Map<String, List<Catalog2Vo>> listMap;
            //try执行业务，业务执行过程中无论是否出现异常，最终都要finally删除锁
            try {
                listMap = getCatalogFromDB();
            }finally {
                //建立lua脚本
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                //执行原子删除脚本
                stringRedisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class), Arrays.asList("lock"),uuid);
                System.out.println("分布式锁已被释放...");
            }
            return listMap;
        }else {
            //没有抢占到锁，再次重试，自旋方式再次抢占锁
            System.out.println("抢占分布式锁失败...正在重新抢占中...");
            try {
                Thread.sleep(1000);
            }catch (Exception e){
            }
            return getCatalogJsonFromDBWithRedisLock();
        }
    }

    //使用本地锁
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithLocalLock() {
        synchronized (this) {
            return getCatalogFromDB();
        }
    }

    //查询数据库中所有的分类
    public Map<String, List<Catalog2Vo>> getCatalogFromDB(){
        //从Redis查询数据是否为空，为空的话到数据库中查询数据
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        //再次判断，加锁之后判断Redis中是否含有数据，不含有则执行查询，含有则返回数据
        if (StringUtils.isEmpty(catalogJson)) {
            //从数据库中查出三级分类
            System.out.println("查询数据库...");
            //查询数据库中所有的分类
            List<CategoryEntity> categoryEntities = this.list();
            //查出所有一级分类
            List<CategoryEntity> level1Categories = getCategoryByParentCid(categoryEntities, "0");
            //遍历所有的一级分类，计算出每个一级分类下的二级分类信息  及三级分类信息
            Map<String, List<Catalog2Vo>> listMap = level1Categories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //遍历查找出二级分类
                List<CategoryEntity> level2Categories = getCategoryByParentCid(categoryEntities, v.getCatId());
                List<Catalog2Vo> catalog2Vos = null;
                if (level2Categories != null) {
                    //封装二级分类到vo并且查出其中的三级分类
                    catalog2Vos = level2Categories.stream().map(cat -> {
                        //遍历查出三级分类并封装
                        List<CategoryEntity> level3Catagories = getCategoryByParentCid(categoryEntities, cat.getCatId());
                        List<Catalog2Vo.Catalog3Vo> catalog3Vos = null;
                        if (level3Catagories != null) {
                            catalog3Vos = level3Catagories.stream()
                                    .map(level3 -> new Catalog2Vo.Catalog3Vo(level3.getParentCid().toString(), level3.getCatId().toString(), level3.getName()))
                                    .collect(Collectors.toList());
                        }
                        Catalog2Vo catalog2Vo = new Catalog2Vo(v.getCatId().toString(), cat.getCatId().toString(), cat.getName(), catalog3Vos);
                        return catalog2Vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
            //将查询结果存储到Redis当中
            String jsonString = JSON.toJSONString(listMap);
            stringRedisTemplate.opsForValue().set("catalogJson", jsonString);
            //返回结果
            return listMap;
        }else {
            //Redis中含有数据，转换成map类型并返回
            Map<String, List<Catalog2Vo>> map = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>(){});
            return map;
        }
    }

    //根据cid计算几级分类信息
    private List<CategoryEntity> getCategoryByParentCid(List<CategoryEntity> categoryEntities, String parentCid) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(cat -> cat.getParentCid().equals(parentCid)).collect(Collectors.toList());
        return collect;
    }



}